package myJs.cps

import myJs.Implicits._
import myJs.cps.myTable._
import myJs.myPkg.i18n.ReactI18next
import myJs.myPkg.jquery.$
import myJs.tool.Pojo._
import myJs.tool.{Pojo, Tool}
import org.scalajs.dom
import org.scalajs.dom.{Element, Event, document}
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._
import slinky.core.annotations.react
import slinky.core.facade.Hooks.{useEffect, useState}
import slinky.core.facade.ReactElement
import slinky.core.{FunctionalComponent, SyntheticEvent, TagElement}
import slinky.web.html._
import typings.csstype.csstypeStrings
import upickle.default._

import scala.collection.immutable.SeqMap
import scala.concurrent.Future
import scala.scalajs.js
import myJs.Implicits._
import myJs.myPkg.reactHookForm.{ReactHookForm, UseFormOptions}

/**
 * Created by yz on 18/3/2022
 */
@react object BootstrapTableCp {

  case class Column(field: String, title: String = "", operable: Boolean = false,
                    formatter: js.UndefOr[(ujson.Value, ujson.Obj) => ReactElement] = js.undefined,
                    searchKind: SearchKind = TextSearch, comVF: (ujson.Value, ujson.Obj) => String,
                    optionMap: SeqMap[String, String] = SeqMap.empty, defaultValue: String = "",
                    checkbox: Boolean = false
                   )

  case class Props(columns: List[Column], datas: List[ujson.Obj],
                   pagination: Boolean = true, pageList: List[Int] = List(10, 25, 50, 100), pageSize: Int = 10,
                   sidePagination: String = "client",
                   ajax: js.UndefOr[(Int, Int, SeqMap[String, SearchData], Option[SortData]) => Future[ujson.Obj]] = js.undefined,
                   selectColumnEnabled: Boolean = false,
                   idField: String = "id",
                   selectedIds: List[String] = List.empty,
                   selectedIdsChange: List[String] => Unit = x => (),
                   search: Boolean = false
                  )

  val component = FunctionalComponent[Props] { props =>

    val useForm = ReactHookForm.useForm(UseFormOptions(mode = "onChange"))

    val (sortField, setSortField) = useState("")
    val (order, setOrder) = useState("")
    val (searchMap, setSearchMap) = useState(SeqMap[String, SearchData]())
    val (tmpSearchMap, setTmpSearchMap) = useState(SeqMap[String, SearchData]())
    val (pageNumber, setPageNumber) = useState(1)
    val (pageSize, setPageSize) = useState(props.pageSize)
    val (menuField, setMenuField) = useState("")
    val (menuShow, setMenuShow) = useState(false)
    val (serverDatas, setServerDatas) = useState(List.empty[ujson.Obj])
    val (serverTotalRows, setServerTotalRows) = useState(0)
    val (columnShowMap, setColumnShowMap) = useState(SeqMap[String, Boolean]())
    val (hasFillDefaultV, setHasFillDefaultV) = useState(false)
    val (allSearch, setAllSearch) = useState("")

    val js.Tuple3(t, i18n, _) = ReactI18next.useTranslation()
    val isServer = props.sidePagination == "server"

    val fieldColumnMap = props.columns.map { x =>
      (x.field, x)
    }.toSeqMap

    object FTool {

      def sortExec = {
        val initDatas = props.datas
        sortField.isBlank
        val (ascSortData, blankDatas) = if (sortField.isBlank) {
          (initDatas, List())
        } else {
          val column = fieldColumnMap(sortField)
          column.searchKind match {
            case Pojo.NumericSearch =>
              val blankDatas = initDatas.filter(_(sortField).toString.isBlank)
              val notBlankDatas = initDatas.filterNot(_(sortField).toString.isBlank).sortBy { x =>
                column.comVF(x(sortField), x).toDouble
              }
              (notBlankDatas, blankDatas)
            case _ =>
              val sortData = initDatas.sortBy(_(sortField).toString)
              (sortData, List())
          }

        }
        order match {
          case "desc" => ascSortData.reverse ::: blankDatas
          case "asc" | "" => ascSortData
        }
      }

      def execFilter(sortDatas: List[ujson.Obj]) = {

        def loop(acc: List[ujson.Obj], searchs: List[(String, SearchData)]): List[ujson.Obj] = {
          searchs match {
            case Nil => acc
            case y :: ys =>
              val curFilterData = acc.filter { row =>
                val field = y._1
                val searchData = y._2
                val column = fieldColumnMap(field)
                searchData match {
                  case DateSearchData(startDate, endDate) =>
                    val dbDate = row(field).myStr
                    val startB = if (startDate.nonBlank) {
                      Tool.timeBefore(startDate, dbDate)
                    } else true
                    val endB = if (endDate.nonBlank) {
                      Tool.timeAfter(endDate, dbDate)
                    } else true
                    startB && endB
                  case TextSearchData(searchType, text) =>
                    val vs = text.split(";")
                    searchType match {
                      case "equals" =>
                        vs.exists(v => row(field).str == v)
                      case "like" =>
                        vs.exists(v => row(field).str.contains(v))
                    }
                  case NumericSearchData(min, max, kind) =>
                    val v = column.comVF(row(field), row)
                    v.isDouble && {
                      val dbDouble = v.toDouble
                      val minB = if (min.isDouble) {
                        dbDouble >= min.toDouble
                      } else true
                      val maxB = if (max.isDouble) {
                        dbDouble <= max.toDouble
                      } else true
                      minB && maxB
                    }
                  case CheckboxSearchData(values) =>
                    values.exists(x => row(field).str.split(";").contains(x))
                }
              }
              loop(curFilterData, ys)
          }
        }

        loop(sortDatas, searchMap.toList)
      }

      def removeSort = () => {
        setSortField("")
        setOrder("")
      }

      def tmp2True(tmpSearchMap: SeqMap[String, SearchData]) = {
        tmpSearchMap.filter { case (field, searchData) =>
          searchData match {
            case DateSearchData(startDate, endDate) => startDate.nonBlank || endDate.nonBlank
            case TextSearchData(searchType, text) => text.nonBlank
            case NumericSearchData(min, max, kind) => min.nonBlank || max.nonBlank
            case CheckboxSearchData(values) => values.nonEmpty
          }
        }
      }

      val removeCondition = (curField: String) => {
        val curTmpSearchMap = tmpSearchMap.updatedWith(curField) { op =>
          op.map { x =>
            x match {
              case y@DateSearchData(startDate, endDate) => y.copy(startDate = "", endDate = "")
              case y@TextSearchData(searchType, text) => y.copy(text = "")
              case y@NumericSearchData(min, max, kind) => y.copy(min = "", max = "")
              case y@CheckboxSearchData(values) => y.copy(values = List.empty[String])
            }
          }
        }
        setTmpSearchMap(curTmpSearchMap)
        val curSearchMap = FTool.tmp2True(curTmpSearchMap)
        setSearchMap(curSearchMap)
      }

      def confirmFilter(curField: String) = () => {
        val curSearchMap = FTool.tmp2True(tmpSearchMap)
        setSearchMap(curSearchMap)
        setMenuShow(false)
      }

      val pageSizeChange = (x: Int) => {
        setPageSize(x)
      }

      val pageNumberChange = (x: Int) => {
        setPageNumber(x)
      }

      def refreshServerDatas = {
        if (isServer) {
          props.ajax.map { ajax =>
            val offset = pageSize * (pageNumber - 1)
            val limit = pageSize
            val sortData = if (sortField.nonBlank) {
              val column = fieldColumnMap(sortField)
              val kind = column.searchKind match {
                case Pojo.NumericSearch => "int"
                case Pojo.DoubleSearch => "double"
                case _ => "str"
              }
              Some(SortData(field = sortField, kind = kind, order = order))
            } else None
            ajax(limit, offset, searchMap, sortData).map { rs =>
              val tableRs = rs("rows").transform(reader[List[ujson.Obj]])
              val total = rs("total").num.toInt
              setServerDatas(tableRs)
              setServerTotalRows(total)
            }
          }
        }
      }

      val columnShowMapChange = (x: SeqMap[String, Boolean]) => {
        setColumnShowMap(x)
      }

      def checkboxChange(curId: String) = (e: SyntheticEvent[TagElement#RefType, Event]) => {
        val checked = e.target.asInstanceOf[dom.html.Input].checked
        if (checked) {
          props.selectedIdsChange(curId :: props.selectedIds)
        } else {
          val newCheckedIs = props.selectedIds.removedV(curId)
          props.selectedIdsChange(newCheckedIs)
        }
      }

      def selectAll(curIds: List[String]) = (e: SyntheticEvent[TagElement#RefType, Event]) => {
        val checked = e.target.asInstanceOf[dom.html.Input].checked
        if (checked) {
          props.selectedIdsChange(curIds)
        } else {
          props.selectedIdsChange(List.empty)
        }
      }

      def allSearchChange = (v: String) => {
        setAllSearch(v)
      }

    }

    val datas = if (isServer) {
      serverDatas
    } else {
      val sortDatas = FTool.sortExec
      FTool.execFilter(sortDatas)
    }

    val totalRows = if (isServer) {
      serverTotalRows
    } else datas.size
    val pageTo = math.min(pageNumber * pageSize, totalRows)

    val pageFrom = (pageNumber - 1) * pageSize + 1

    val curPageDatas = if (isServer) {
      datas
    } else datas.slice(pageFrom - 1, pageTo)


    useEffect(() => {
      $(document).on("click", (y: Element, e: Event) => {
        val trueY = e.target.asInstanceOf[Element]
        val b = $(trueY).hasClass("select2-selection__choice__remove") ||
          $(trueY).hasClass("select2-search__field") ||
          $(trueY).hasClass("filterContent") ||
          $(trueY).parents().hasClass("filterContent")
        if (!b) {
          setMenuShow(false)
        }
      })
    }, List()
    )

    useEffect(() => {
      FTool.refreshServerDatas
    }, List(pageSize, pageNumber, sortField, order, searchMap)
    )

    useEffect(() => {
      props.selectedIdsChange(List.empty)
    }, List(pageSize, pageNumber, sortField, order, searchMap, props.datas)
    )

    useEffect(() => {
      if (hasFillDefaultV) {
        props.columns.headOption.map { column =>
          FTool.confirmFilter(column.field)()
        }
        setHasFillDefaultV(false)
      }
    }, List(hasFillDefaultV)
    )

    useEffect(() => {
      val initTmpSearchMap = props.columns.map { x =>
        val searchData = (x.searchKind match {
          case DateSearch => DateSearchData(startDate = "", endDate = "")
          case TextSearch => TextSearchData(searchType = "like", text = "")
          case NumericSearch => NumericSearchData(min = "", max = "", kind = "int")
          case DoubleSearch => NumericSearchData(min = "", max = "", kind = "double")
          case CheckboxSearch =>
            val curVs = if (x.defaultValue.isBlank) List.empty[String] else List(x.defaultValue)
            CheckboxSearchData(values = curVs)
        }).asInstanceOf[SearchData]
        (x.field, searchData)
      }.toSeqMap
      setTmpSearchMap(initTmpSearchMap)
      setHasFillDefaultV(true)

      val initColumnMap = if (props.selectColumnEnabled) {
        props.columns.zipWithIndex.map { case (column, curI) =>
          (column.field, curI < 8)
        }.toSeqMap
      } else {
        props.columns.zipWithIndex.map { case (column, curI) =>
          (column.field, true)
        }.toSeqMap
      }
      setColumnShowMap(initColumnMap)
    }, List(props.columns)
    )

    div(
      if (props.selectColumnEnabled) {
        SelectColumnCp(columnShowMap = columnShowMap, fieldColumnMap = fieldColumnMap,
          columnShowMapChange = FTool.columnShowMapChange)
      } else null,
      FilterConditionCp(searchMap = searchMap, fieldColumnMap = fieldColumnMap,
        removeCondition = FTool.removeCondition),
      SortConditionCp(fieldColumnMap = fieldColumnMap, sortField = sortField, order = order,
        removeSort = FTool.removeSort),
      div(className := "table-responsive",

        div(className := "bootstrap-table bootstrap3",

          div(className := "fixed-table-toolbar",

            if (props.search) {
              div(className := "pull-right search input-group",
                PureInputCp(useForm = useForm, selfClassName = "fsearch-input", placeHolder = "Search", autoComplete = "off",
                  value = allSearch, onChange = FTool.allSearchChange)
              )
            } else null,

          ),

          div(style := Style(paddingBottom = 0, overflowY = csstypeStrings.auto, minHeight = 350), className := "fixed-table-container",
            div(className := "fixed-table-body", style := Style(overflow = "visible"),
              table(style := Style(wordWrap = csstypeStrings.`break-word`), id := "myTable",
                className := "display table table-hover table-bordered",
                thead(
                  tr(
                    props.columns.zipWithIndex.filter { case (column, curI) =>
                      columnShowMap.getOrElse(column.field, true)
                    }.map { case (column, curI) =>
                      if (column.checkbox) {
                        val curIds = curPageDatas.map { curData =>
                          curData.value.getOrElse(props.idField, ujson.Null).myStr
                        }
                        val selectedAll = curIds.toSet == props.selectedIds.toSet
                        th(key := s"${column.field}", className := "bs-checkbox", style := Style(width = 36),
                          div(className := "th-inner",
                            label(
                              input(name := "btSelectAll", `type` := "checkbox", onChange := FTool.selectAll(curIds),
                                checked := selectedAll),
                              span()
                            )
                          ),
                          div(className := "fht-cell")
                        )
                      } else {
                        th(key := s"${column.field}",
                          div(className := "th-inner", style := Style(overflow = "visible"),
                            s"${column.title} ",
                            FilterButtonCp(useForm = useForm, column = column, sortField = sortField, sortFieldChange = setSortField,
                              order = order, orderChange = setOrder, menuShow = menuShow,
                              menuShowChange = setMenuShow,
                              menuField = menuField, menuFieldChange = setMenuField,
                              tmpSearchMap = tmpSearchMap,
                              confirmFilter = FTool.confirmFilter,
                              tmpSearchMapChange = setTmpSearchMap)
                          ),
                          div(className := "fht-cell")
                        )
                      }
                    },
                  )
                ),
                tbody(
                  if (datas.isEmpty) {
                    tr(className := "no-records-found",
                      td(colSpan := props.columns.size, t("noMatches"))

                    )
                  } else {
                    curPageDatas.zipWithIndex.map { case (curData, curI) =>
                      val any = curData.value.getOrElse(props.idField, ujson.Null)
                      val curId = any.myStr
                      val selectedCls = if (props.selectedIds.contains(curId)) {
                        "selected"
                      } else ""
                      val checkedV = props.selectedIds.contains(curId)
                      tr(key := s"${curId}", data - "index" := s"${curI}", className := s"${selectedCls}",
                        props.columns.filter { column =>
                          columnShowMap.getOrElse(column.field, true)
                        }.map { column =>
                          if (column.checkbox) {
                            td(key := s"${column.field}", className := "bs-checkbox", style := Style(width = 36),
                              div(className := "th-inner",
                                label(
                                  input(name := "btSelectItem", `type` := "checkbox", onChange := FTool.checkboxChange(curId),
                                    checked := checkedV
                                  ),
                                  span()
                                )
                              )
                            )
                          } else {
                            val any = curData.value.getOrElse(column.field, ujson.Null)
                            val ele = column.formatter.map { f =>
                              f(any, curData)
                            }.getOrElse {
                              span(curData(column.field).str)()
                            }
                            td(key := s"${column.field}", ele)
                          }
                        }
                      )
                    }
                  }

                )
              ),
            )
          ),
          PaginationCp(totalRows = totalRows, pageFrom = pageFrom, pageTo = pageTo, pageList = props.pageList,
            pageSize = pageSize, pageSizeChange = FTool.pageSizeChange, pageNumber = pageNumber,
            pageNumberChange = FTool.pageNumberChange)
        ),
      ),
    )


  }


}
